import pickle
import traceback
from inspect import Parameter, signature
from typing import Any, Callable, Dict, List, Type, Union
from tornado.escape import json_decode
from tornado.web import RequestHandler


def unwrap_multi_value_dict(d: Dict[str, List[bytes]]) -> Dict[str, bytes]:
    """把一个一对多的字典转换为一对一的字典（每个键值对取最后一个值）

    Args:
        d (Dict[str, List[bytes]]): 一对多字典

    Returns:
        Dict[str, bytes]: 一对一字典
    """
    result = {}
    for key in d:
        result.update({key: d[key][-1]})
    return result


def cast_bool(b: str) -> bool:
    """强转bool

    Args:
        b (str): 布尔字符值

    Returns:
        bool: 布尔实体
    """
    b = b.lower()
    return b == "1" or b == "true"


def pickle_input_adapter(func: Callable) -> Callable[[bytes], Any]:
    """python_only==True时对入参校验和适配进处理方法

    Args:
        func (Callable): 处理方法

    Returns:
        input_adapter (bytes) -> (Any): 适配器
    """
    sig = signature(func)

    def input_adapter(body: bytes) -> Any:
        inputs: dict = pickle.loads(body)
        if not isinstance(inputs, dict):
            raise ExposedError("入参必须为{'args':[...], 'kwargs':{...}}的格式!")

        args = inputs.get("args", [])
        if not isinstance(args, list):
            raise ExposedError("args参数必须是list格式!")

        kwargs = inputs.get("kwargs", {})
        if not isinstance(kwargs, dict):
            raise ExposedError("kwargs参数必须是dict格式!")
        try:
            sig.bind(*args, **kwargs)
        except TypeError as e:
            raise ExposedError(code=400, message=str(e))
        return func(*args, **kwargs)
    return input_adapter


def json_input_adapter(func: Callable) -> Callable[[bytes], Any]:
    """json透传时对入参校验和适配进处理方法

    Args:
        func (Callable): 处理方法

    Returns:
        Callable (bytes) -> (Any): 适配器
    """
    sig = signature(func)

    args_name = None
    kwargs_name = None
    for param_name, param in sig.parameters.items():
        if param.kind == Parameter.VAR_POSITIONAL:
            args_name = param_name
        elif param.kind == Parameter.VAR_KEYWORD:
            kwargs_name = param_name

    def input_adapter(body: bytes) -> Any:
        inputs: dict = json_decode(body)
        if not isinstance(inputs, dict):
            raise ExposedError("入参必须为JSON Object的格式!")

        if args_name in inputs:
            args = inputs[args_name]
            if not isinstance(args, list):
                raise ExposedError("args参数必须是JSON Array格式!")
            inputs.pop(args_name)
        else:
            args = []

        if kwargs_name in inputs:
            kwargs = inputs[kwargs_name]
            if not isinstance(kwargs, dict):
                raise ExposedError("args参数必须是JSON Object格式!")
            inputs.pop(kwargs_name)
        else:
            kwargs = {}

        kwargs.update(inputs)

        try:
            sig.bind(*args, **kwargs)
        except TypeError as e:
            raise ExposedError(code=400, message=str(e))
        return func(*args, **kwargs)

    return input_adapter


def query_adapter_with_cast(func: Callable) -> Callable[[Dict[str, bytes]], Any]:
    """GET和POST对入参校验和适配进处理方法

    Args:
        func (Callable): 要包装的方法

    Raises:
        TypeError: 被包装的方法有错误的参数形式 (*args, **kwargs)

    Returns:
        Dict[str, List[bytes]] -> Any: 被包装后的方法
    """
    sig = signature(func)

    # 闭包存储参数的强转类型
    cast_dict = {}
    for parameter in sig.parameters.values():
        if parameter.kind != Parameter.POSITIONAL_OR_KEYWORD:
            # 被暴露为服务的方法不可以用*args或**kwargs为参数
            raise TypeError(
                "被暴露为服务的方法不可以用*args或**kwargs为参数, not a valid expose-needed function, should not have parameter [%s]" % parameter.name)

        if parameter.annotation is not None:
            if parameter.annotation == bool:
                # 特化bool类型强转
                cast_dict.update({parameter.name: cast_bool})
            else:
                cast_dict.update({parameter.name: parameter.annotation})
        else:
            # 如果参数注解为空则默认强转为str类型
            cast_dict.update({parameter.name: str})

    # 真正处理传入参数的函数
    def input_adapter(query: Dict[str, bytes]) -> Any:
        parsed_inputs = {}
        for raw_key in query:
            # TODO: 此处预计应有一个 驼峰式/帕斯卡式 → 下划线式 的转换
            key = raw_key

            if key not in cast_dict:
                # 多余的参数不报错，只跳过
                continue

            t = cast_dict[key]
            v = query[raw_key]
            if t != bytes:
                v = v.decode("utf-8")
            parsed_inputs[key] = t(query[raw_key])

        try:
            sig.bind(**parsed_inputs)
        except TypeError as e:
            raise ExposedError(code=400, message=str(e))
        return func(**parsed_inputs)

    return input_adapter


class ExposedError(Exception):
    """
    只是一个要暴露给用户的错误标识，返回给用户的错误信息用此类或其子类包装
    """

    def __init__(self, code: int, message: str) -> None:
        self.code = code
        self.message = message

    def __str__(self) -> str:
        return "[错误码: %d] %s: %s" % (self.code, type(self).__name__, self.message)


class ServiceRequestHandler(RequestHandler):
    """
    继承RequestHandler抽象类，动态生成处理器的基类

    GET或DELETE方法: query_arguments -> Dict[str, bytes] -> Dict[str, str] -> query_adapter_with_cast
    !Note(服务端方法): 不可含有*args和**kwargs, 保证入参为int,float,str,bool类型
    !Note(客户端请求): 对bool类型进行特化包装

    POST或PUT方法(json): body -> dict(JSON) -> json_input_adapter
    !Note(服务端验证): 保证JSON的args和kwargs参数的类型为JSONArray和JSONObject

    POST方法(python_only): body -> pickle.loads(xxx) -> Dict[str, Union[dict, list]] -> pickle_input_adapter
    !Note(服务端验证): 保证入参为dict, 且为如下格式: {"args":[...], "kwargs":{...}}
    """

    def get(self):
        if not self._check_method("get"):
            return
        query_arguments = unwrap_multi_value_dict(self.request.query_arguments)
        self._common_request(query_arguments, query_adapter_with_cast)

    def post(self):
        if not self._check_method("post"):
            return
        if type(self).python_only:
            self._common_request(self.request.body, pickle_input_adapter)
        else:
            self._common_request(self.request.body, json_input_adapter)

    def delete(self):
        if not self._check_method("delete"):
            return
        query_arguments = unwrap_multi_value_dict(self.request.query_arguments)
        self._common_request(query_arguments, query_adapter_with_cast)

    def put(self):
        if not self._check_method("put"):
            return
        if type(self).python_only:
            self._common_request(self.request.body, pickle_input_adapter)
        else:
            self._common_request(self.request.body, json_input_adapter)

    def _check_method(self, method: str) -> bool:
        if method not in type(self).accepted_methods:
            wrapped_result = {
                "code": 405,
                "message": "method '%s' is not accepted" % method
            }
            self.finish(wrapped_result)
            return False
        return True

    def _common_request(self, data: Union[bytes, Dict[str, bytes]], adapter: Callable[[Callable], Callable]):
        try:
            result = adapter(type(self).service_handler)(data)
            wrapped_result = {
                "code": 0,
                "result": result
            }

        except ExposedError as e:
            # 要暴露给用户的错误
            result = str(e)
            wrapped_result = {
                "code": e.code,
                "message": e.message
            }
        except Exception as e:
            # 其余记录错误日志，供开发者查看
            traceback.print_exc()
            wrapped_result = {
                "code": -1,
                "message": "unknown error"
            }
        finally:
            if type(self).python_only:
                # 如果是python_only，直传持久化的Python类
                self.finish(pickle.dumps(wrapped_result))
            else:
                # 默认使用json格式传输
                self.finish(wrapped_result)


def adapt_handler(func: Callable, methods: List[str], _python_only: bool) -> Type[RequestHandler]:
    """取得处理方法对应的RequestHandler类

    Args:
        func (Callable): 服务处理函数
        methods (List[str]): 可接受的http方法
        _python_only: 是否只对python客户端提供服务

    Returns:
        Type[RequestHandler]: 服务处理函数对应的RequestHandler类
    """
    class _service_request_handler(ServiceRequestHandler):
        service_handler = func
        accepted_methods = methods
        python_only = _python_only

    return _service_request_handler
